/* JSEV - JSONSchema Extension Validator
 *
 * Copyright (c) 2009 Seth Wessitsh (seth@wessitsh.com)
 * Licensed under the MIT (MIT-LICENSE.txt) licence.
 */

var JSON = JSON ? JSON : {};
JSON.schema = JSON.schema ? JSON.schema : {};
JSON.schema.ext = JSON.schema.ext ? JSON.schema.ext : {};

/**
	@interface {
		id: "JSON.schema.ext.validate",

		description: "
			This function validates a schema as an extension of all of the types
			listed in the `extends' restriction if present, a so called explict 
			extension.  It also validates a schema as an extension of those types 
			found in the `type' restriction and the type `any', i.e., an implict 
			extension.  Lastly, it validates inter-restriction relationships.

			For more information on the meaning of all of this see the JSEV spec
			at <url here>.
		",

		parameters: [
			{ name: "schema", type: "JSONSchema",
				description: "A schema to validate as an implict/explict extension" } ],

		returns: { type: "object",
			properties: {
				valid: { type: "boolean",
					description: 
						"True if the `schema' parameter is a valid implict/explict extension,
						 false otherwise" },

				errors: { type: "array",
					description: 
						"An array of errors encountered while validating the `schema' parameter",

					items: { type: "object",
						properties: {
							code: { type: "string",
								description: "A string id indicating the type of error" },

							data: { type: "object", 
								description: "A collection of data relevant to the error" } } } } } }
 */
JSON.schema.ext.validate = function jseValidate(schema) {
	var retVal = {valid: true, errors: []},
		pe = function pushError(e) {
			if (e instanceof Array) {
				retVal.valid = retVal.valid && e.length === 0;
				retVal.errors = retVal.errors.concat(e);
			} else {
				retVal.valid = false;
				retVal.errors[retVal.errors.length] = e;
			}
		},
		walker = new JSON.schema.ext.walker(schema),
		LV = new JSON.schema.ext.validate.LocalValidator(),
		CV = new JSON.schema.ext.validate.ComparitorValidator(),
		node;

	// check that schema is an object or string in the registry
	switch (Object.typeName(schema)) {
		case "string":
		case "object": 
			if (schema !== "" && schema !== null) {
				break;
			}
		// deliberate fall through when if doesn't match
		default:
			pe(new JSON.schema.err.TypeError(
				"TE000", {
					current: {
						schema: schema,
						path: "$",
						value: schema
					},
					requires: { value: "object" }
				}
			));

			return retVal;
	}

	if (typeof(schema) == "string" && !(schema in JSON.schema.registry)) {
		pe(new JSON.schema.err.InvalidValueError(
			"IVE000", {
				current: {
					path: "$",
					value: schema
				}
			}
		));

		return retVal;
	}

	// Validate the schema locally
	LV.validate(schema, schema, "$", "$");
	pe(LV.errors);

	// Skip the root schema, we've alredy checked that with LV
	walker.next();

	// Walk schema hierarchy comparing `schema' to each node (ancestor schema)
	while (walker.hasNext()) {
		try {
			node = walker.next();
			CV.validate(schema, node, "$", walker.path());
			pe(CV.errors);
		} catch (e) {
			pe(e);
		}
	}
	
	return retVal;
};

/**
	@interface {
		description: "
			The local validator checks that a schema is valid according to the types required for 
			each restriction to be present as well as inter-restriction relationships within the 
			current schema only.",

		methods: {
			// @todo write interface documentation for each method 
		} }
 */
JSON.schema.ext.validate.LocalValidator = function jsevLocalValidator() {
	this.isSubType = JSON.schema.ext.isSubType;
	this.err = JSON.schema.err;
};
JSON.schema.ext.validate.LocalValidator.prototype = {
	/* Each method takes two schema parameters in order to retain the same
	   interface as the ComparitorValidator, however, sA and sB should
	   refer to the same schema, as such pA and pB should have the same paths. */
	validate: function jsevlvValidate(sA, sB, pA, pB) {
		this.errors = [];
		this.valid = true;
		this.pe = function pushError(e) {
			this.valid = false;
			this.errors[this.errors.length] = e;
		};

		for (var prop in sA) {
			if (this[prop] instanceof Function) {
				this[prop](sA, sB, pA, pB);
			}
		}

		return this.valid;
	},
	"extends": function jsevlvExtends(sA, sB, pA, pB) {
		var property = "extends";

		// Restriction has valid type?
		switch (Object.typeName(sA[property])) {
			case "string":
			case "object":
				if (sA[property] !== "" && sA[property] !== null) {
					break;
				}
			// deliberate fall through when if doesn't match
			default:
				this.pe(new this.err.TypeError(
					"TE000", {
						current: {
							schema: sA,
							path: pA + "." + property,
							value: sA[property] === null ? 
								"null" : Object.typeName(sA[property])
						},
						requires: { value: "[object, string]" }
					}
				));
		}

		// If value is a string, is it in the registry?
		if (
			typeof(sA[property]) === "string" && 
			!(sA[property] in JSON.schema.registry)
		) {
			this.pe(new this.err.InvalidValueError(
				"IVE000", {
					current: {
						schema: sA, 
						path: pA + "." + property, 
						value: sA[property]
					}
				}
			));
		}
	},
	id: function jsevlvId(sA, sB, pA, pB) {
		var property = "id";

		// Restriction has valid type?
		if (typeof(sA[property]) !== "string") {
			this.pe(new this.err.TypeError(
				"TE000", {
					current: {
						schema: sA,
						path: pA + "." + property,
						value: sA[property] === null ? 
							"null" : Object.typeName(sA[property])
					},
					requires: { value: "string" }
				}
			));
		}

		// If the registry is present, is the id already defined?
		else if (JSON.schema.registry) {
			if (
				sA[property] in JSON.schema.registry && 
				sA !== JSON.schema.registry[sA[property]]
			) {
				this.pe(new this.err.InvalidValueError(
					"IVE001", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: {
							schema: JSON.schema.registry[sA[property]],
							path: "JSON.schema.registry." + sA[property]
						}
					}
				));
			} else {
				// Register the schema
				JSON.schema.registry[sA[property]] = sA;
			}
		}
	},
	type: function jsevlvType(sA, sB, pA, pB) {
		var property = "type",
			stes;

		/** @todo check that type does not contain extends 
		    @todo check for conflicts with disallow */

		switch (Object.typeName(sA[property])) {
			case "array":
				for (var i in sA[property]) {
					if (!sA[property].hasOwnProperty(i)) {
						continue;
					}

					var goodType = true;
					switch (Object.typeName(sA[property][i])) {
						case "string":
							// Type not in registry
							if (
								sA[property][i] !== "" &&
								sA[property][i] !== null && 
								!(sA[property][i] in JSON.schema.registry)
							) {
								this.pe(new this.err.InvalidValueError(
									"IVE000", {
										current: {
											schema: sA, 
											path: pA + "." + property + "[" + i + "]", 
											value: sA[property][i]
										}
									}
								));
							}
						break;
						case "object": 
							if (sA[property][i] !== null) {
								break;
							}
						// deliberate fall through when if doesn't match
						default:
							goodType = false;
							// Array element has bad type
							this.pe(new this.err.TypeError(
								"TE000", {
									current: {
										schema: sA,
										path: 
											pA + "." + property + "[" + i + "]",
										value: sA[property][i]
									},
									requires: { value: "[string, object]" }
								}
							));
					}

					// Current type subtype of extends?
					if (
						goodType && 
						sA["extends"] && 
						!this.isSubType(
							sA[property][i], 
							sA["extends"],
							pA + "." + property + "[" + i + "]",
							"$.extends",
							(stes = {valid: true, errors: []})
						)
					) {
						this.pe(new this.err.SubTypeError(
							"STE000", {
								current: {
									schema: sA, 
									path: pA + "." + property + "[" + i + "]", 
									value: sA[property][i]
								},
								conflictsWith: {
									schema: sA,
									path: "$.extends",
									value: sA["extends"]
								},
								causedBy: stes.errors
							}
						));
					}
				}
			break;
			case "string":
				// Type in registry?
				if (!(sA[property] in JSON.schema.registry)) {
					this.pe(new this.err.InvalidValueError(
						"IVE000", {
							current: {
								schema: sA, 
								path: pA + "." + property, 
								value: sA[property]
							}
						}
					));
				}

			// deliberate fall through

			case "object":
				if (sA[property] !== "" && sA[property] !== null) {
					// Subtype of extends?
					if (
						sA["extends"] && 
						!this.isSubType(
							sA[property], 
							sA["extends"],
							pA + "." + property,
							"$.extends",
							(stes = {valid: true, errors: []})
						)
					) {
						this.pe(new this.err.SubTypeError(
							"STE000", {
								current: {
									schema: sA, 
									path: pA + "." + property,
									value: sA[property]
								},
								conflictsWith: {
									schema: sA,
									path: "$.extends",
									value: sA["extends"]
								},
								causedBy: stes.errors
							}
						));
					}

					break;
				}
			default:
				// Restriction has bad type.
				this.pe(new this.err.TypeError(
					"TE000", {
						current: {
							schema: sA,
							path: pA + "." + property,
							value: sA[property] === null ? 
								"null" : Object.typeName(sA[property])
						},
						requires: { value: "[object, string]" }
					}
				));
		}
	},
	disallow: function jsevlvDisallow(sA, sB, pA, pB) {
		var property = "disallow";

		if (sA.type) {
			var tIsArr = sA.type instanceof Array,
				tA = tIsArr ? sA.type : [sA.type],
				dIsArr = sA[property] instanceof Array,
				dA = dIsArr ? sA[property] : [sA[property]],
				subOfAtLeastOne = false,
				steArr = [],
				stes;

			/* each type in current restriction (dA) must be a strict subtype of extends
			   (if present) or a subtype of at least one type in current type restriction (tA) */
			for (var i=0; i < dA.length; i++) {
				if (
					!sA["extends"] ||
					!this.isSubType(
						dA[i], sA["extends"],
						pA + "." + property + (dIsArr ? "[" + i + "]" : ""),
						pB + ".extends",
						(stes = {valid: true, errors: []}),
						true
					)
				) {
					if (sA["extends"]) {
						steArr = steArr.concat(stes.errors);
					}

					for (var j=0; j < tA.length; j++) {
						if (
							this.isSubType(
								dA[i], tA[j],
								pA + "." + property + (dIsArr ? "[" + i + "]" : ""),
								pB + ".type" + (tIsArr ? "[" + j + "]" : ""),
								(stes = {valid: true, errors: []}),
								!tIsArr
							)
						) {
							subOfAtLeastOne = true;
							break;
						} else {
							steArr = steArr.concat(stes.errors);
						}
					}
				}

				if (! subOfAtLeastOne ) {
					this.pe(new this.err.SubTypeError(
						"STE000", {
							current: {
								schema: sA,
								path: pA + "." + property + (dIsArr ? "[" + i + "]" : ""),
								value: sA[property]
							},
							conflictsWith: { 
								schema: sA,
								path: "$.type",
								value: sA.type
							},
							causedBy: steArr
						}
					));
				}

				subOfAtLeastOne = false;
			}
		}
	},
	properties: function jsevlvProperties(sA, sB, pA, pB) {
		this._chkTypeAllowed_("properties", sA, pA, "object");
	},
	items: function jsevlvItems(sA, sB, pA, pB) {
		var property = "items";

		this._chkTypeAllowed_("items", sA, pA, "array");

		// Conflicts with minItems or maxItems?
		if (sA[property] instanceof Array) {
			if (
				sA.minItems && 
				sA.additionalItems === false &&
				sA[property].length < sA.minItems
			) {
				/* Not enough schemas in items
				   due to minItems & additionalItems === false */
				this.pe(new this.err.InvalidValueError(
					"IVE002", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: [
							{
								schema: sA,
								path: "$.minItems",
								value: sA.minItems
							},
							{
								schema: sA,
								path: "$.additionalItems",
								value: sA.additionalItems
							}
						]
					}
				));
			} 

			if (
				sA.maxItems && 
				sA[property].length > sA.maxItems
			) {
				// Too many schemas in items due to maxItems
				this.pe(new this.err.InvalidValueError(
					"IVE003", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: {
							schema: sA,
							path: "$.maxItems",
							value: sA.maxItems
						}
					}
				));
			}
		}
	},
	additionalProperties: function jsevlvAdditionalProperties(sA, sB, pA, pB) {
		this._chkTypeAllowed_("additionalProperties", sA, pA, "object");
	},
	additionalItems: function jsevlvAdditionalItems(sA, sB, pA, pB) {
		var property = "additionalItems";

		this._chkTypeAllowed_(property, sA, pA, "array");

		if (!(sA.items instanceof Array)) {
			this.pe( new this.err.RequiredTypeError(
				"RTE001", {
					current: {schema: sA, path: pA + "." + property},
					requires: {path: "$.items", value: "tuple type"}
				}
			) );
		}

		/* Has the maximum number of items been reached?
		   (due to items.length == maxItems) */
		if (
			sA[property] !== false && 
			sA.items instanceof Array && 
			typeof(sA.maxItems) == "number" && 
			sA.items.length == sA.maxItems
		) {
			this.pe( new this.err.InvalidValueError(
				"IVE008", {
					current: {
						schema: sA,
						path: pA + "." + property, 
						value: sA[property]
					},
					conflictsWith: [ {
						schema: sA, 
						path: "$.items", 
						value: sA.items
					}, {
						schema: sA,
						path: "$.maxItems",
						value: sA.maxItems
					} ]
				}
			) );
		}
	},
	minimum: function jsevlvMinimum(sA, sB, pA, pB) {
		this._chkTypeAllowed_("minimum", sA, pA, "number");
	},
	maximum: function jsevlvMaximum(sA, sB, pA, pB) {
		this._chkTypeAllowed_("maximum", sA, pA, "number");
	},
	minItems: function jsevlvMinItems(sA, sB, pA, pB) {
		var property = "minItems";
		// restriction has valid type? (integer)
		if (sA[property] % 1 > 0) {
			this.pe(new this.err.InvalidValueError(
				"IVE014", {
					current: {
						schema: sA,
						path: pA + "." + property,
						value: sA[property]
					}
				}
			));
		}
		this._chkTypeAllowed_("minItems", sA, pA, "array");
	},
	maxItems: function jsevlvMaxItems(sA, sB, pA, pB) {
		var property = "maxItems";
		// restriction has valid type? (integer)
		if (sA[property] % 1 > 0) {
			this.pe(new this.err.InvalidValueError(
				"IVE014", {
					current: {
						schema: sA,
						path: pA + "." + property,
						value: sA[property]
					}
				}
			));
		}
		this._chkTypeAllowed_("maxItems", sA, pA, "array");
	},
	maxDecimal: function jsevlvMaxDecimal(sA, sB, pA, pB) {
		var property = "maxDecimal";
		// restriction has valid type? (integer)
		if (sA[property] % 1 > 0) {
			this.pe(new this.err.InvalidValueError(
				"IVE014", {
					current: {
						schema: sA,
						path: pA + "." + property,
						value: sA[property]
					}
				}
			));
		}
		this._chkTypeAllowed_("maxDecimal", sA, pA, "number");
	},
	_chkTypeAllowed_: function jsevlvChkTypeAllowed(property, sA, pA, type) {
		try {
			if (!JSON.schema.ext.allowsType(sA, type)) {
				this.pe(new this.err.RequiredTypeError(
					"RTE000", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						requires: {value: type}
					}
				));
			}
		} catch (e) {
			this.pe(e);
		}
	}
};

/**
	@interface {
		description: "
			The comparitor validator checks that a schema is valid by comparing its restrictions
			to those of another schema",

		methods: {
			// @todo write interface documentation for each method } }
 */
JSON.schema.ext.validate.ComparitorValidator = function jsevComparitorValidator() {
	this.isSubType = JSON.schema.ext.isSubType;
	this.err = JSON.schema.err;
};
JSON.schema.ext.validate.ComparitorValidator.prototype = {
	validate: function jsevcvValidate(sA, sB, pA, pB) {
		this.valid = true;
		this.errors = [];
		this.pe = function pushError(e) {
			this.valid = false;
			this.errors[this.errors.length] = e;
		};

		// validate each restriction in sA against that in sB
		for (var prop in sA) {
			if (
				sA.hasOwnProperty(prop) && 
				this[prop] instanceof Function
			) {
				this[prop](sA, sB, pA, pB);
			}
		}

		return this.valid;
	},
	type: function jsevcvType(sA, sB, pA, pB) {
		var property = "type";

		if (sB.disallow) {
			var aIsArr = sA[property] instanceof Array,
				bIsArr = sB.disallow instanceof Array,
				sAA = aIsArr ? sA[property] : [sA[property]],
				sBA = bIsArr ? sB.disallow : [sB.disallow];

			for (var i=0; i < sAA.length; i++) {
				for (var j=0; j < sBA.length; j++) {
					if (
						sAA[i] != sB && 
						JSON.schema.registry[sAA[i]] != sB &&
						this.isSubType(
							sAA[i], 
							sBA[j], 
							pA + property + (aIsArr ? "[" + i + "]" : ""),
							pB + ".disallow" + (bIsArr ? "[" + j + "]" : "")
						)
					) {
						// i'th type is a subtype of j'th disallow, and therefore is disallowed
						this.pe(new this.err.SubTypeError(
							"STE013", {
								current: {
									schema: sA, 
									path: pA + "." + property + (aIsArr ? "[" + i + "]" : ""),
									value: sAA[i]
								},
								conflictsWith: {
									schema: sB, 
									path: pB + ".disallow" + (bIsArr ? "[" + j + "]" : ""), 
									value: sBA[j]
								}
							}
						));
					}
				}
			}
			
		}	
	},
	properties: function jsevcvProperties(sA, sB, pA, pB) {
		var property = "properties",
			stes;

		if (sB[property]) {
			for (var prop in sA[property]) {
				if (prop in sB[property]) {
					if (
						! this.isSubType(
							sA[property][prop], 
							sB[property][prop],
							pA + "." + property + "." + prop,
							pB + "." + property + "." + prop,
							(stes = {valid: true, errors: []})
						)
					) {
						// corresponding property is not a subtype
						this.pe(new this.err.SubTypeError(
							"STE001", {
								current: {
									schema: sA, 
									path: pA + "." + property + "." + prop, 
									value: sA[property][prop]
								},
								conflictsWith: {
									schema: sB, 
									path: pB + ".properties." + prop, 
									value: sB[property]
								},
								causedBy: stes.errors
							}
						));
					}
				} else if (sB.additionalProperties === false) {
					// property introduction not allowed
					this.pe(new this.err.IntroductionError(
						"IE000", {
							current: {
								schema: sA, 
								path: pA + "." + property + "." + prop, 
								value: sA[property][prop]
							},
							conflictsWith: {
								schema: sB,
								path: pB + ".additionalProperties",
								value: sB.additionalProperties
							}
						}
					));
				} else if (typeof(sB.additionalProperties) == "object") {
					if (
						!this.isSubType(
							sA[property][prop], 
							sB.additionalProperties,
							pA + "." + property + "." + prop,
							pB + ".additionalProperties",
							(stes = {valid: true, errors: []})
						) 
					) {
						// property not subtype of additionalProperties
						this.pe(new this.err.SubTypeError(
							"STE002", {
								current: {
									schema: sA, 
									path: pA + "." + property + "." + prop, 
									value: sA[property][prop]
								},
								conflictsWith: {
									schema: sB,
									path: pB + ".additionalProperties",
									value: sB.additionalProperties
								},
								causedBy: stes.errors
							}
						));
					}
				}
			}
		}
	},
	items: function jsevcvItems(sA, sB, pA, pB) {
		var property = "items", i, stes;

		// Conflicts with minItems or maxItems?
		if (sA[property] instanceof Array) {
			if (
				sB.minItems && 
				sA.additionalItems === false &&
				sA[property].length < sB.minItems
			) {
				/* Not enough schemas in items
				   due to minItems & additionalItems === false */
				this.pe(new this.err.InvalidValueError(
					"IVE002", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: [ {
							schema: sB,
							path: pB + ".minItems",
							value: sB.minItems
						}, {
							schema: sA,
							path: pA + ".additionalItems",
							value: sA.additionalItems
						} ]
					}
				));
			} 

			if (
				sB.maxItems && 
				sA[property].length > sB.maxItems
			) {
				// Too many schemas in items due to maxItems
				this.pe(new this.err.InvalidValueError(
					"IVE003", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: {
							schema: sB,
							path: pB + ".maxItems",
							value: sB.maxItems
						}
					}
				));
			}
		}

		// Conflicts due to change from tuple to non-tuple?
		if (
			sB[property] instanceof Array && 
			sA[property] !== undefined && 
			!(sA[property] instanceof Array)
		) {
			if (sB.additionalItems === false) {
				/* Finite set of items to infinite set
				   due to super items == tuple and super additionalItems == false 
				   and current items == non tuple */
				this.pe(new this.err.InvalidValueError(
					"IVE004", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: [ {
							schema: sB,
							path: pB + ".items",
							value: sB[property]
						}, {
							schema: sB,
							path: pB + ".additionalItems",
							value: sB.additionalItems
						} ]
					}
				));
			} else {
				// items must be subtype of all schemas in super items
				var errors = [];
				for (i in sB[property]) {
					if (
						! this.isSubType(
							sA[property], 
							sB[property][i],
							pA + "." + property,
							pB + "." + property + "[" + i + "]",
							(stes = {valid: true, errors: []})
						)
					) {
						errors[errors.length] = new this.err.SubTypeError(
							"STE000", {
								current: {
									schema: sA, 
									path: pA + "." + property, 
									value: sA[property]
								},
								conflictsWith: {
									schema: sB, 
									path: pB + ".items[" + i + "]", 
									value: sB.items[i]
								},
								causedBy: stes.errors
							}
						);
					}
				}
				if (errors.length > 0) {
					this.pe(new this.err.SubTypeError(
						"STE003", {
							current: {
								schema: sA, 
								path: pA + "." + property, 
								value: sA[property]
							},
							conflictsWith: {
								schema: sB, 
								path: pB + ".items", 
								value: sB.items
							},
							causedBy: errors
						}
					));
				}

				// items must be a subtype of super additionalItems if present
				if (
					(typeof(sB.additionalItems) == "object" || 
					typeof(sB.additionalItems) == "string") && 
					!this.isSubType(
						sA[property], 
						sB.additionalItems,
						pA + "." + property,
						pB + ".additionalItems",
						(stes = {valid: true, errors: []})
					)
				) {
					this.pe(new this.err.SubTypeError(
						"STE004", {
							current: {
								schema: sA, 
								path: pA + "." + property, 
								value: sA[property]
							},
							conflictsWith: {
								schema: sB,
								path: pB + ".additionalItems",
								value: sB.additionalItems
							},
							causedBy: stes.errors
						}
					));
				}
			}

		// Change from non-tuple to tuple?
		} else if (
			sB[property] !== undefined && 
			!(sB[property] instanceof Array) && 
			sA[property] instanceof Array
		) {
			for (i in sA[property]) {
				if (
					sA[property].hasOwnProperty(i) &&
					!this.isSubType(
						sA[property][i], 
						sB[property],
						pA + "." + property + "[" + i + "]",
						pB + "." + property,
						(stes = {valid: true, errors: []})
					)
				) {
					// schemas in the tuple must be subtypes of super items
					this.pe(new this.err.SubTypeError(
						"STE005", {
							current: {
								schema: sA, 
								path: pA + "." + property, 
								value: sA[property]
							},
							conflictsWith: {
								schema: sB,
								path: pB + "." + property,
								value: sB[property]
							},
							causedBy: stes.errors
						}
					));
				}
			}

		// Super tuple and current tuple?
		} else if (
			sB[property] instanceof Array && 
			sA[property] instanceof Array
		) {
			if (
				sB[property].length < sA[property].length && 
				sB.additionalItems === false
			) {
				/* Too many schemas in the current items restriction
				   due to super items and super additionalItems */
				this.pe(new this.err.InvalidValueError(
					"IVE005", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: [ {
								schema: sB,
								path: pB + "." + property,
								value: sB[property]
							}, {
								schema: sB,
								path: pB + ".additionalItems",
								value: sB.additionalItems
							} 
						]
					}
				));
			}

			var l = sB[property].length <= sA[property].length ? 
				sB[property].length : 
				sA[property].length;

			for (i=0; i < l; i++) {
				if (
					!this.isSubType(
						sA[property][i], 
						sB[property][i],
						pA + "." + property + "[" + i + "]",
						pB + "." + property + "[" + i + "]",
						(stes = {valid: true, errors: []})
					)
				) {
					/* schemas in items must be subtypes of corresponding schemas
					   in super items */
					this.pe(new this.err.SubTypeError(
						"STE006", {
							current: {
								schema: sA, 
								path: pA + "." + property + "[" + i + "]", 
								value: sA[property][i]
							},
							conflictsWith: {
								schema: sB, 
								path: pB + ".items[" + i + "]", 
								value: sB[property][i]
							},
							causedBy: stes.errors
						}
					));
				}
			}

			// Super additionalItems defined and not equivalent to false? (!= is intentional)
			if (sB.additionalItems != false) {
				for (; i < sA[property].length; i++) {
					if (
						!this.isSubType(
							sA[property][i], 
							sB.additionalItems,
							pA + "." + property + "[" + i + "]",
							pB + ".additionalItems",
							(stes = {valid: true, errors: []})
						)
					) {
						/* remaining schemas in items must be subtypes of additionalItems */
						this.pe(new this.err.SubTypeError(
							"STE007", {
								current: {
									schema: sA, 
									path: pA + "." + property + "[" + i + "]", 
									value: sA[property][i]
								},
								conflictsWith: {
									schema: sB,
									path: pB + ".additionalItems",
									value: sB.additionalItems
								},
								causedBy: stes.errors
							}
						));
					}
				}
			}

		// Non-tuple to non-tuple?
		} else if (
			typeof(sB[property]) == "object" && 
			typeof(sA[property]) == "object"
		) {
			if (
				!this.isSubType(
					sA[property], 
					sB[property],
					pA + "." + property,
					pB + "." + property,
					(stes = {valid: true, errors: []})
				)
			) {
				// items must be a subtype of super items
				this.pe(new this.err.SubTypeError(
					"STE004", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: {
							schema: sB,
							path: pB,
							value: sB[property]
						},
						causedBy: stes.errors
					}
				));
			}
		}
	},
	additionalProperties: function jsevcvAdditionalProperties(sA, sB, pA, pB) {
		var property = "additionalProperties", stes, except, prop;

		if (typeof(sB[property]) == "object") {
			if (
				! this.isSubType(
					sA[property], 
					sB[property],
					pA + "." + property,
					pB + "." + property,
					(stes = {valid: true, errors: []})
				)
			) {
				// restriction not subtype of ancestor restriction
				this.pe( new this.err.SubTypeError(
					"STE004", {
						current: {
							schema:sA, 
							path:pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: {
							schema: sB,
							path: pB + "." + property,
							value: sB[property]
						},
						causedBy: stes.errors
					}
				) );
			}
		}
		
		if (sB.properties && sA.properties) {
			except = Object.except( sB.properties, sA.properties );

			for (prop in except) {
				if ( 
					!this.isSubType( 
						sA[property], 
						except[prop],
						pA + "." + property,
						pB + ".properties." + prop,
						(stes = {valid: true, errors: []})
					)
				) {
					// additionalProperties not subtype of non-overridden property
					this.pe( new this.err.SubTypeError(
						"STE008", {
							current: {
								schema: sA, 
								path: pA + "." + property, 
								value: sA[property]
							},
							conflictsWith: {
								schema: sB,
								path: pB + ".properties." + prop, 
								value: sB.properties[prop]
							},
							causedBy: stes.errors
						}
					) );
				}
			}
		}

		if (sA[property] === false) {
			except = Object.except( 
				JSON.schema.required(sB.properties), 
				JSON.schema.required(sA.properties)
			);

			for (prop in except) {
				if (!except.hasOwnProperty(prop)) {
					continue;
				}

				var code = (
					prop in sB.properties && 
					prop in sA.properties
				) ? "IVE006" : "IVE007";

				// value false not allowed as required properties haven't been overridden
				// or are no longer required
				this.pe( new this.err.InvalidValueError(
					code, {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: {
							schema: sB,
							path: pB + ".properties." + prop, 
							value: sB.properties[prop]
						}
					}
				) );
			}
		}
	},
	additionalItems: function jsevcvAdditionalItems(sA, sB, pA, pB) {
		var property = "additionalItems", stes;

		if (sA[property] !== false) {
			if (
				Object.typeName(sB.items) == "object" &&
				!this.isSubType(
					sA[property], 
					sB.items,
					pA + "." + property,
					pB + ".items",
					(stes = {valid: true, errors: []})
				)
			) {
				// not a subtype of super items restriction
				this.pe( new this.err.SubTypeError(
					"STE004", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: {
							schema: sB,
							path: pB + ".items",
							value: sB.items
						},
						causedBy: stes.errors
					}
				) );
			}

			if (
				sA.items instanceof Array && 
				typeof(sB.maxItems) == "number" && 
				sA.items.length == sB.maxItems
			) {
				// current restriction would allow more items than are allowed by 
				// ancestor maxItems and ancestor items restrictions
				this.pe( new this.err.InvalidValueError(
					"IVE008", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: [ {
							schema: sA, 
							path: pA + ".items", 
							value: sA.items 
						}, {
							schema: sB,
							path: pB + ".maxItems",
							value: sB.maxItems
						} ]
					}
				) );

			} else if (
				sB[property] === false && 
				sA.items instanceof Array && 
				sB.items instanceof Array && 
				sA.items.length == sB.items.length
			) {
				// current restriction would allow more items than are allowed by
				// ancestor items and ancestor additionalItems
				this.pe( new this.err.InvalidValueError(
					"IVE009", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: sA[property]
						},
						conflictsWith: [ {
							schema: sA, 
							path: pA + ".items", 
							value: sA.items
						}, {
							schema: sB,
							path: pB + ".items",
							value: sB.items
						}, {
							schema: sB,
							path: pB + "." + property,
							value: sB[property]
						} ]
					}
				) );
			}
		}

		if (
			sA.items instanceof Array && 
			sB.items instanceof Array && 
			sA.items.length < sB.items.length
		) {
			var l = sB.items.length > (sA.maxItems || sB.maxItems) ? 
				(sA.maxItems || sB.maxItems) : 
				sB.items.length;

			for (var i=sA.items.length; i < l; i++) {
				if (
					!this.isSubType(
						sA[property], 
						sB.items[i],
						pA + "." + property,
						pB + ".items[" + i + "]",
						(stes = {valid: true, errors: []})
					)
				) {
					// current restriction is not a subtype of all non-overridden items
					this.pe( new this.err.SubTypeError(
						"STE009", {
							current: {
								schema: sA, 
								path: pA + "." + property, 
								value: sA[property]
							},
							conflictsWith: [ {
								schema: sA,
								path: pA + ".items",
								value: sA.items
							}, {
								schema: sB,
								path: pB + ".items[" + i + "]", 
								value: sB.items[i]
							}],
							causedBy: stes.errors
						}
					) );
				}
			}
		}

		// Validate current additionalItems against super additionalItems if necessary
		if (
			typeof(sB[property]) == "object" && (
				!(sA.maxItems || sB.maxItems) || 
				(
					sB.items instanceof Array && 
					(
						sA.maxItems > sB.items.length ||
						sB.maxItems > sB.items.length
					)
				)
			)
		) {
			// additionalItems not a subtype of super additionalItems?
			if (
				sA[property] !== false &&
				!this.isSubType(
					sA[property], 
					sB[property], 
					pA + "." + property,
					pB + "." + property,
					(stes = {valid: true, errors: []})
				)
			) {
				if (!(sB.maxItems || sA.maxItems)) {
					/* Current restriction not a subtype of ancestor restriction;
					   required due to lack of maxItems restriction */
					this.pe( new this.err.SubTypeError(
						"STE010", {
							current: {
								schema: sA, 
								path: pA + "." + property, 
								value: sA[property]
							},
							conflictsWith: [ {
								schema: sB,
								path: pB + "." + property,
								value: sB[property]
							}, {
								path: "maxItems"
							} ],
							causedBy: stes.errors
						}
					) );
				} else {
					var mi = sA.maxItems !== undefined ?
						{ 
							schema: sA,
							path: pA + ".maxItems",
							value: sA.maxItems
						} : {
							schema: sB,
							path: pB + ".maxItems",
							value: sB.maxItems
						};

					/* Current restriction not a subtype of ancestor restriction;
					   required due to maxItems being greater than items.length */
					this.pe( new this.err.SubTypeError(
						"STE011", {
							current: {
								schema: sA, 
								path: pA + "." + property, 
								value: sA[property]
							},
							conflictsWith: [ {
								schema: sB,
								path: pB + ".items",
								value: sB.items
							}, {
								schema: sB,
								path: pB + "." + property,
								value: sB[property]
							}, mi ],
							causedBy: stes.errors
						}
					) );
				}
			}
		}
	},
	minimum: function jsevcvMinimum(sA, sB, pA, pB) {
		this._chkGtEqSuper_("minimum", sA, sB, pA, pB);
	},
	maximum: function jsevcvMaximum(sA, sB, pA, pB) {
		this._chkLtEqSuper_("maximum", sA, sB, pA, pB);
	},
	minItems: function jsevcvMinItems(sA, sB, pA, pB) {
		this._chkGtEqSuper_("minItems", sA, sB, pA, pB);
	},
	maxItems: function jsevcvMaxItems(sA, sB, pA, pB) {
		this._chkLtEqSuper_("maxItems", sA, sB, pA, pB);
	},
	maxDecimal: function jsevcvMaxDecimal(sA, sB, pA, pB) {
		this._chkLtEqSuper_("maxDecimal", sA, sB, pA, pB);
	},
	"enum": function jsevcvEnum(sA, sB, pA, pB) {
		var property = "enum";

		if (sB[property]) {
			var except = Array.except(sA[property], sB[property]);

			for (var i=0; i < except.length; i++) {
				/* Current restriction not a subset of ancestor restriction
				   i.e., a new value has been introduced */
				this.pe( new this.err.IntroductionError(
					"IE001", {
						current: {
							schema: sA, 
							path: pA + "." + property, 
							value: except[i]
						},
						conflictsWith: {
							schema: sB,
							path: pB + "." + property,
							value: sB[property]
						}
					}
				) );
			}
		}
	},
	_chkGtEqSuper_: function jsevcvGtEqSuper(property, sA, sB, pA, pB) {
		if (
			typeof(sB[property]) == "number" && 
			sA[property] < sB[property]
		) {
			// current restriction not greater than or equal to ancestor restriction
			this.pe( new this.err.InvalidValueError(
				"IVE010", {
					current: {
						schema: sA, 
						path: pA + "." + property, 
						value: sA[property]
					},
					conflictsWith: {
						schema: sB, 
						path: pB + "." + property, 
						value: sB[property]
					}
				}
			));
		}
	},
	_chkLtEqSuper_: function jsevcvLtEqSuper(property, sA, sB, pA, pB) {
		if (
			typeof(sB[property]) == "number" && 
			sA[property] > sB[property]
		) {
			// current restriction not less than or equal to ancestor restriction
			this.pe( new this.err.InvalidValueError(
				"IVE011", {
					current: {
						schema: sA, 
						path: pA + "." + property, 
						value: sA[property]
					},
					conflictsWith: {
						schema: sB, 
						path: pB + "." + property, 
						value: sB[property]
					}
				}
			));
		}
	}
};

/**
	@interface {
		name: "JSON.schema.nameOf",

		description: 
			"Attempt to get the name of the schema parameter in the form of a JSONQuery path",

		parameters: [
			{ name: "schema", type: ["JSONSchema", "string"],
				"The schema to get the name of" } ],

		returns: { type: "string",
			description: "
				- If parameter `schema' is a string this will return the string [?id="<schema>"]
				  Where <schema> will be replaced with the string value of parameter `schema'.

				- If parameter `schema' is an object and contains an id property this will return 
				  the string [?id="<schema.id>"].  Where <schema.id> will be replaced with the 
				  string value of the id property of parameter `schema'.

				- If parameter `schema' is an object and contains a type property this will return 
				  the string [?type="<schema.type>"].  Where <schema.type> will be replaced with the
				  string value of the type property of parameter `schema'.

				- If any of the above properties are null or not present the empty string is 
				  returned
			" 
		}
	}
*/
JSON.schema.nameOf = function nameOf(schema) {
	switch (Object.typeName(schema)) {
		case "string":
			if (schema !== "" && schema !== null) {
				return "[?id=\"" + schema + "\"]";
			} else {
				return "";
			}
		break;
		case "object":
			return schema !== null ? 
				(schema.id ? 
					"[?id=\"" + schema.id + "\"]" : 
					schema.type ? 
						"[?type=\"" + schema.type + "\"]" : 
						"") : 
				"";
		default: 
			return "";
	}
};

/**
	@interface {
		name: "JSON.schema.required",

		description: 
			"Retrieves those properties from the `properties' argument which
			have no optional flag or which have the optional flag set to false.",

		parameters: [
			{ name: "properties", type: "object",
				description: "An object which is a JSONSchema properties restriction."}],

		returns: { type: "object",
			description: 
				"An object with all the properties in `properties' which do not have 
				the optional flag or have the optional flag set to false." }
	}
 */
JSON.schema.required = function jsRequired(properties) {
	var retVal = {};

	for (var prop in properties) {
		if (
			properties.hasOwnProperty(prop) && (
				!("optional" in properties[prop]) || 
				!properties[prop].optional
			)
		) {
			retVal[prop] = properties[prop];
		}
	}
	
	return retVal;
};

/**
	@interface {
		name: "JSON.schema.isSubType",

		description: 
			"Determines whether one schema is a subtype of another.
			
			All schemas are subtypes of themselves and the type `any'.  That is, any
			schema can be used in place of itself or the type `any' with no observable
			changes in behavior.

			In general one schema is a subtype of another if it is a valid extension
			of the other according to JSON.schema.ext.validate and all types in 
			sA.extends and sA.type are subtypes of sB.extends and subtypes of at 
			least one of sB.type",
		
		parameters: [
			{name: "sA", type: ["KnownSchemaName", "JSONSchema"],
				description: "A schema which may or may not be a subtype of `sB'."},

			{name: "sB", type: ["KnownSchemaName", "JSONSchema"],
				description: "The schema `sA' is to be compared against."},
			
			{name: "pA", type: "string", optional: true,
				description: "A JSONPath/JSONQuery indicating the path to `sA'"},

			{name: "pB", type: "string", optional: "true",
				description: "A JSONPath/JSONQuery indicating the path to `sB'."},

			{name: "err", type: "array",
				description: 
					"An array to be populated with the errors found in the schema",
				items: { type: "object" } ],

		returns: { type: "boolean",
			description: 
				"true if `sA' is a subtype of `sB'.  See the interface description for the 
				rules which describe what a subtype is." }
	}
 */
JSON.schema.ext.isSubType = function isSubType(sA, sB, pA, pB, err, strict) {
	strict = !!strict;
	pA = pA ? pA : "$";
	pB = pB ? pB : "$";
	err = err ? err : {valid: true, errors: []};

	function pe(e) {
		if (e instanceof Array) {
			err.errors = err.errors.concat(e);
			err.valid = err.valid && e.length === 0;
		} else {
			err.errors[err.errors.length] = e;
			err.valid = false;
		}
	}
	
	if (
		typeof(sA) == "string" && typeof(sB) == "string" &&
		sA in JSON.schema.registry.baseTypes && sB in JSON.schema.registry.baseTypes
	) {
		var rv = JSON.schema.ext.derivesFrom(sA, sB);
		if (!rv) {
			pe(new JSON.schema.err.SubTypeError(
				"STE012", {
					current: { path: pA, value: sA },
					conflictsWith: { path: pB, value: sB }
				}
			));
		}
		return rv;
	}

	if (typeof(sA) == "string") {
		if (!(sA in JSON.schema.registry)) {
			pe(new JSON.schema.err.InvalidValueError(
				"IVE000", {
					current: {
						schema: sA, 
						path: pA
					}
				}
			));

			return false;
		} else {
			sA = JSON.schema.registry[sA];
		}
	}

	if (typeof(sB) == "string") {
		if(!(sB in JSON.schema.registry)) {
			pe(new JSON.schema.err.InvalidValueError(
				"IVE000", {
					current: {
						schema: sB, 
						path: pB
					}
				}
			));

			return false;
		} else {
			sB = JSON.schema.registry[sB];
		}
	}

	/* Same type, not strict? All types are subtypes of themselves (i.e., they 
	   can be used in place of themselves with no observable change in behavior) 
	   but a type is not a strict subtype of itself. */
	if (strict === false && (sA === sB || sB === JSON.schema.registry.any)) {
		return true;
	}

	var aTypes=[], bTypes=[], aPaths=[], bPaths=[], i;
	
	if (sA["extends"]) {
		aTypes[aTypes.length] = sA["extends"];
		aPaths[aPaths.length] = pA + ".extends";
	}

	if (sA.type) {
		if (sA.type instanceof Array) {
			for (i=0; i < sA.type.length; i++) {
				aTypes[aTypes.length] = sA.type[i];
				aPaths[aPaths.length] = pA + ".type[" + i + "]"; 
			}
		} else {
			aTypes[aTypes.length] = sA.type;
			aPaths[aPaths.length] = pA + ".type";
		}
	}

	if (sB.type) {
		if (sB.type instanceof Array) {
			for (i=0; i < sB.type.length; i++) {
				bTypes[bTypes.length] = sB.type;
				bPaths[bPaths.length] = pB + ".type[" + i + "]";
			}
		} else {
			bTypes[bTypes.length] = sB.type;
			bPaths[bPaths.length] = pB + ".type";
		}
	}

	/** Each type specified by sA.extends and sA.type must be a subtype 
	    of sB.extends and a subtype of at least one type in sB.type

		@todo speed this up if possible (i.e., different algorithm) */
	if (sB["extends"]) {
		for (i=0; i < aTypes.length; i++) {
			if (!this.isSubType( aTypes[i], sB["extends"], aPaths[i], pB + ".extends", err)) {
				return false;
			}
		}
	}

	if (bTypes.length > 0) {
		for (i=0; i < aTypes.length; i++) {
			var subAtLeastOne = false,
				retVal = {valid: true, errors: []};

			for (var j=0; j < bTypes.length; j++) {
				if (this.isSubType( aTypes[i], bTypes[j], aPaths[i], bPaths[j], retVal )) {
					subAtLeastOne = true;
					break;
				}
			}
			if (!subAtLeastOne) {
				pe(new JSON.schema.err.SubTypeError(
					"STE014", {
						current: {
							schema: sA,
							path: aPaths[i],
							value: aTypes[i]
						},
						conflictsWith: {
							schema: sB,
							path: pB + ".type",
							value: pB.type
						},
						causedBy: retVal.errors
					}
				));
			}
		}
	}

	var walker = new JSON.schema.ext.walker(sB, pB),
		CV = new JSON.schema.ext.validate.ComparitorValidator();

	// Walk sB's hierarchy comparing `sA' to each node
	while (walker.hasNext()) {
		try {
			var node = walker.next();
			CV.validate(sA, node, pA, walker.path());
			pe(CV.errors);
		} catch (e) {
			pe(e);
		}
	}

	return err.valid;
};

/**
	@interface {
		name: "JSON.schema.ext.derivesFrom",

		description: "
			Determines whether one type/schema is an extension, directly or indirectly, implicitly
			or explicitly, of another
			
			Notes:
				- All types/schemas derive from themselves and type any
				- A union type U derives from a single, or union, type T if at least one type 
				  in U derives from type T, or at least one of the types in T.
		",

		parameters: [
			{ name: "derivation", type: ["KnownSchemaName", "JSONSchema"],
				description: "A schema which may or may not derive from the `schema' parameter" },

			{ name: "schema", type: ["KnownSchemaName", "JSONSchema"],
				description: 
					"A schema to check derivation against.  If the value is a JSONSchema and
					it has either an id property or a type property then the value of the first
					one present in order of (id, type) will be searched for in the hierarchy of 
					`derivation'.
					Note: that this won't function properly if the values are not strings.
					@todo fix this." },
		],

		returns: { type: "boolean",
			description: "
				true if parameter `derivation' is an implict or explict extension, directly or 
				indirectly, of parameter `schema', false otherwise."
		}
	}
*/
JSON.schema.ext.derivesFrom = function jseDerivesFrom(derivation, schema) {
	var walker = new JSON.schema.ext.walker(derivation),
		allowed = false,
		foundAtDepth = -1,
		node;

	switch (Object.typeName(schema)) {
		case "object":
			schema =
				("id" in schema) ? 
					schema.id
				: ("type" in schema) ? 
					schema.type 
				: "any";
		// deliberate fall through
		case "array": case "string": break;
		default:
			throw new JSON.schema.err.TypeError(
				"TE000", {
					current: {
						schema: schema,
						path: "$",
						value: schema
					},
					requires: { value: "[object, array, string]" }
				}
			);
	}

	while (walker.hasNext()) {
		node = walker.next();

		/* Ensure that we search an entire branch before
		   before returning true */
		if (allowed && walker.depth() <= foundAtDepth) {
			return true;
		}

		var s = node.type && node.id ? [node.id].concat(node.type) :
			node.id ? node.id : node.type ? node.type : undefined;

		// node contains the schema in either the type or id restriction
		if (s && Array.contains(s, schema)) {
			allowed = true;
			foundAtDepth = walker.depth();
		}

		// node contains the schema in the disallow restriction
		if (node.disallow && Array.contains(node.disallow, schema)) {
			allowed = false;
			walker.nextBranch();
		}
	}
	
	return allowed;
};

/**
	@interface {
		name: "JSON.schema.ext.allowsType",

		description: "
			Walks the extension hierarchy of the given schema to determine whether it 
			allows a given type or not.  I.e., whether a type is not implicitly 
			disallowed (type integer implicitly disallows all other types), or not 
			explicitly disallowed (via the disallow restriction).

			Notes:
				- The type `any' allows all types
				- Specifying a type restriction with a single type or union type implicitly
				  disallows all types not in the restriction.
				- Specifying a disallow restriction with a single type or union type explicitly
				  disallows all types in the restriction (implicitly allowing all others).
				- A type is allowed if at least one type in the type restriction allows it (directly
				  or indiretly) and the schema does not extend a type which disallows it implictily 
				  or explicitly.",

		parameters: [
			{ name: "derivation", type: ["KnownSchemaName", "JSONSchema"],
				description: "A schema which may or may not allow the type specified by `schema'" },

			{ name: "schema", type: ["KnownSchemaName", "JSONSchema"],
				description: 
					"A schema which may or may not be allowed by `derivation's hierarchy. If the 
					value is a JSONSchema and it has either an id property or a type property then 
					the value of the first one present in order of (id, type) will be searched for 
					in the hierarchy of `derivation'.
					Note: that this won't function properly if the values are not strings.
					@todo fix this." } ],

		returns: { type: "boolean",
			description: 
				"true if the extension hierarchy of `derivation' allows the type specified by 
				`schema', false otherwise." }
	}
*/
JSON.schema.ext.allowsType = function jseAllowsType(derivation, schema) {
	var walker = new JSON.schema.ext.walker(derivation),
		allowed = false,
		foundAtDepth = -1,
		node,
		baseTypes = ["string", "object", "array", "boolean", "number", "null"];

	switch (Object.typeName(schema)) {
		case "object":
			schema =
				("id" in schema) ? 
					[schema.id, "any"]
				: ("type" in schema) ? 
					[schema.type, "any"]
				: "any";
		break;
		case "string": 
			schema = [schema, "any"];
		break;
		case "array": 
			schema = schema.concat("any");
		break;
		default:
			throw new JSON.schema.err.TypeError(
				"TE000", {
					current: {
						schema: schema,
						path: "$",
						value: schema
					},
					requires: { value: "[object, array, string]" }
				}
			);
	}

	while (walker.hasNext()) {
		node = walker.next();

		/* Ensure that we search an entire branch before
		   before returning true */
		if (allowed && walker.depth() <= foundAtDepth) {
			return true;
		}

		var s = node.type && node.id ? [node.id].concat(node.type) :
			node.id ? node.id : node.type ? node.type : undefined;

		// node either allows any type or contains the type (schema) we're looking for
		if (!node.type || (s && Array.contains(s, schema))) {
			allowed = true;
			foundAtDepth = walker.depth();
		}

		// node explicitly disallows the type (schema) or
		// implicitly disallows the type and consists of only basetypes
		// (i.e., types defined in the JSON specification)
		if (
			(node.disallow && Array.contains(node.disallow, schema)) ||
			(node.type && 
				(!Array.contains(node.type, schema) && 
				Array.except(node.type, baseTypes).length === 0))
		) {
			allowed = false;
			if (walker.branchType() == "extends") {
				walker.nextBranch(-1);
			} else {
				walker.nextBranch();
			}
		}
	}

	return allowed;
};